#pragma once

#include <iostream>
#include <memory>
#include <sys/select.h>
#include "Socket.hpp"

using namespace Socket_ns;

const static unsigned int N = sizeof(fd_set) * 8;
class SelectServer
{
    const static int defaultFd = -1;

public:
    SelectServer(uint16_t port)
        : _port(port), _listenSock(std::make_unique<TCPSocket>())
    {
        InetAddr addr("0", _port);
        _listenSock->BuildListenSocket(addr);
        for (int i = 0; i < N; i++)
        {
            _fd_array[i] = defaultFd;
        }
        _fd_array[0] = _listenSock->SockFd(); // 监听套接字已就绪
    }

    // 通过fd，进行IO操作
    void ServiceIO(int pos_fd)
    {
        char buffer[1024];
        ssize_t bytes = recv(_fd_array[pos_fd], buffer, sizeof(buffer) - 1, 0);
        if (bytes == 0)
        {
            LOG(DEBUG, "%d is closed\n", _fd_array[pos_fd]);
            // 客户端关闭连接/不再发送消息
            // 服务器需要关闭该连接
            ::close(_fd_array[pos_fd]);
            _fd_array[pos_fd] = defaultFd;

            LOG(DEBUG, "curr fd_array[] fd list : %s\n", RfdsToString().c_str());
        }
        else if (bytes < 0)
        {
            LOG(DEBUG, "%d recv error\n", _fd_array[pos_fd]);
            ::close(_fd_array[pos_fd]);
            _fd_array[pos_fd] = defaultFd;
            LOG(DEBUG, "curr fd_array[] fd list : %s\n", RfdsToString().c_str());
        }
        else
        {
            buffer[bytes] = 0;
            std::cout << "client say@ " << buffer << std::endl;
            std::string response = "server echo# ";
            response += buffer;
            ::send(_fd_array[pos_fd], response.c_str(), response.size(), 0);
        }
    }

    // 处理监听套接字，与客户端建立连接
    void AcceptClient()
    {
        InetAddr clientAddr;
        int sockfd = _listenSock->Accepter(&clientAddr);
        if (sockfd < 0)
        {
            LOG(DEBUG, "Accepter error");
            return;
        }

        LOG(INFO, "Create new link, sockfd: %d, client info: %s:%d", sockfd, clientAddr.IP().c_str(), clientAddr.Port());
        // 将sockfd添加到辅助数组中，使用select等待

        // 1. 查找辅助数组中空缺的位置
        int pos = 1;
        for (; pos < N; pos++)
        {
            if (_fd_array[pos] == defaultFd)
                break;
        }
        // 2. 添加
        if (pos == N)
        {
            // 没有空缺位置，select不能再管理fd-->关闭该fd
            ::close(sockfd);
            LOG(WARNING, "server is full!\n");
            return;
        }
        else
        {
            _fd_array[pos] = sockfd;
            LOG(INFO, "%d add to select array!\n", sockfd);
        }
        LOG(DEBUG, "curr fd_array[] fd list : %s\n", RfdsToString().c_str());
    }

    // 处理已就绪的文件描述符
    void HandlerEvent(fd_set &rfds)
    {
        // 从辅助数组中查找已经就绪的文件描述符
        for (int i = 0; i < N; i++)
        {
            // 不在辅助数组中的fd一定没有就绪
            if (_fd_array[i] == defaultFd)
                continue;

            // 如果当前文件描述符没有就绪(rfds中的fd是真正就绪的)，判断下一个
            if (FD_ISSET(_fd_array[i], &rfds))
            {
                // 如果当前就绪的文件描述符是监听套接字，就去accept
                if (_fd_array[i] == _listenSock->SockFd())
                {
                    AcceptClient(); // 监听结束后，select新获取的fd，等待client发送请求
                }
                else
                {
                    // 如果当前就绪的文件描述符是普通文件描述符，就去IO操作
                    ServiceIO(i);
                }
            }
        }
    }

    void Loop()
    {
        while (true)
        {
            // accept等待连接，也是在等待读取条件满足，可以使用select
            fd_set rfds;
            FD_ZERO(&rfds); // 先清空一下位图内容

            // 将辅组数组中的文件描述符添加到rfds中，进行select
            int max_fd = defaultFd;
            for(int i = 0; i < N; i++)
            {
                if(_fd_array[i] == defaultFd)
                    continue;
                FD_SET(_fd_array[i], &rfds);

                // 更新最大fd
                if(max_fd < _fd_array[i])
                    max_fd = _fd_array[i];
            }

            struct timeval timeout = {1, 0}; // 一秒返回
            int ret = select(max_fd + 1, &rfds, nullptr, nullptr, nullptr);
            switch (ret)
            {
            case -1:
                LOG(ERROR, "select error...");
                break;
            case 0:
                LOG(INFO, "listen timeout...");
                break;
            default:
                LOG(DEBUG, "Event Happen...");
                HandlerEvent(rfds);
                break;
            }
        }
    }

    ~SelectServer() = default;

private:
    // 将辅助数组中的值转换为字符串
    std::string RfdsToString()
    {
        std::string ret;
        for (int i = 0; i < N; i++)
        {
            if (_fd_array[i] == defaultFd)
                continue;
            ret += std::to_string(_fd_array[i]);
            ret += " ";
        }
        return ret;
    }

private:
    uint16_t _port;
    std::unique_ptr<Socket> _listenSock;
    int _fd_array[N]; // 辅助数组 -- 保存当前文件描述符（不一定就绪）
};
